﻿using System.Collections.ObjectModel;

namespace SmallGeo;

#region PolygonVisitor    
/// <summary>
/// polygon访问器,可以方便的遍历每个角和边
/// </summary>
public class PolygonVisitor
{
    private List<Vector2d> points;
    internal PolygonVisitor(List<Vector2d> points, double tolerance = 0.0001)
    {
        if (!Util.IsNearySimplePolygon(points, tolerance)) throw new Exception($"只能对简单多边形开启 PolygonVisitor !");
        if (points.First() == points.Last()) this.points = points.SkipLast(1).Select(i => i).ToList();
        else this.points = points.Select(i => i).ToList();

        var radians = Util.GetPolygonRadians(this.points);
        var normals = Util.GetPolygonEdgeNormals(this.points);

        var cs = this.points;
        var corners = new List<CornerVisitor>();
        var edges = new List<EdgeVisitor>();
        for (int i = 0; i < cs.Count; i++)
        {
            var coord = cs[i];
            var nextCoord = cs[(i + 1) % cs.Count];
            var corner = new CornerVisitor
            {
                Position = coord,
                Polygon = this,
                Radian = radians[i],
                Index = i,
            };
            corners.Add(corner);
            var edge = new EdgeVisitor
            {
                Polygon = this,
                Length = nextCoord.DistanceTo(coord),
                InnerNormal = normals[i],
                Index = i,
            };
            edges.Add(edge);
        }
        Corners = new ReadOnlyCollection<CornerVisitor>(corners);
        Edges = new ReadOnlyCollection<EdgeVisitor>(edges);
    }
    public List<Vector2d> Points => points.ToList();
    public ReadOnlyCollection<CornerVisitor> Corners { get; private set; }
    public ReadOnlyCollection<EdgeVisitor> Edges { get; private set; }

    private CornerVisitor _nextCorner(CornerVisitor current)
    {
        var index = Corners.IndexOf(current);
        return Corners[(index + 1) % Corners.Count];
    }
    private CornerVisitor _preCorner(CornerVisitor current)
    {
        var index = Corners.IndexOf(current);
        return Corners[(index - 1 + Corners.Count) % Corners.Count];
    }
    private EdgeVisitor _nextEdge(EdgeVisitor current)
    {
        var index = Edges.IndexOf(current);
        return Edges[(index + 1) % Corners.Count];
    }
    private EdgeVisitor _preEdge(EdgeVisitor current)
    {
        var index = Edges.IndexOf(current);
        return Edges[(index - 1 + Edges.Count) % Edges.Count];
    }

    private CornerVisitor _nextCorner(EdgeVisitor current)
    {
        var index = Edges.IndexOf(current);
        return Corners[(index + 1) % Corners.Count];
    }
    private CornerVisitor _preCorner(EdgeVisitor current)
    {
        var index = Edges.IndexOf(current);
        return Corners[(index + Corners.Count) % Corners.Count];
    }
    private EdgeVisitor _nextEdge(CornerVisitor current)
    {
        var index = Corners.IndexOf(current);
        return Edges[(index) % Edges.Count];
    }
    private EdgeVisitor _preEdge(CornerVisitor current)
    {
        var index = Corners.IndexOf(current);
        return Edges[(index - 1 + Edges.Count) % Edges.Count];
    }

    public class EdgeVisitor
    {
        internal EdgeVisitor() { }
        public double Length { get; internal set; }
        public int Index { get; internal set; }
        public Vector2d InnerNormal { get; internal set; }

        public PolygonVisitor Polygon { get; internal set; }

        public EdgeVisitor NextEdge() => Polygon._nextEdge(this);
        public EdgeVisitor PreEdge() => Polygon._preEdge(this);
        public CornerVisitor PreCorner() => Polygon._preCorner(this);
        public CornerVisitor NextCorner() => Polygon._nextCorner(this);
        public LineSegment2d Line => new LineSegment2d { P0 = this.PreCorner().Position, P1 = this.NextCorner().Position };
    }

    public class CornerVisitor
    {
        internal CornerVisitor() { }
        public Vector2d Position { get; internal set; }
        public double Radian { get; internal set; }
        public bool IsSame90Degree(double degreeeTolerance = 1) => Radian.ToDegree().IsNearlySame(90, degreeeTolerance);
        public bool IsSame270Degree(double degreeeTolerance = 1) => Radian.ToDegree().IsNearlySame(270, degreeeTolerance);
        public int Index { get; internal set; }

        public PolygonVisitor Polygon { get; internal set; }
        public CornerVisitor NextCorner() => Polygon._nextCorner(this);
        public CornerVisitor PreCorner() => Polygon._preCorner(this);
        public EdgeVisitor NextEdge() => Polygon._nextEdge(this);
        public EdgeVisitor PreEdge() => Polygon._preEdge(this);

        public bool IsConcave(double radianTolerance = 0.01)
        {
            return Math.Abs(Radian - radianTolerance) > Math.PI;
        }

        public bool IsPerpendicular(double radianTolerance = 0.01)
        {
            return Math.Abs(Radian % Math.PI) < radianTolerance;
        }

        public bool IsStraight(double radianTolerance = 0.01)
        {
            return Math.Abs(Radian - Math.PI) < radianTolerance;
        }
    }
}
#endregion
